#include <stdlib.h>
#include <malloc.h>

/* MEMORIA PRINCIPAL */
struct palavra * MEM;
int maxMem;
int startIndex;
int startIndexPrint;
int endIndexPrint;

struct palavra {
    unsigned long int p : 40;
};

typedef struct {
    unsigned int IR : 8;
    unsigned int IBR : 20;
    unsigned int PC : 12;
    unsigned int MAR : 12;
    unsigned int leftRequired : 1;
    unsigned int nextInstIBR : 1;
} PCU; //Program Control Unit

typedef struct {
    unsigned long int AC : 40;
    unsigned long int MQ : 40;
    unsigned long int MBR : 40;
    void (*operacao)();    
} ALU; //Arithmetic-logic Unit


extern PCU uc;
extern ALU ula;

/* Descrição:
 * Procedure que fornece acesso aos dados na memória principal. 
 * Faz o registrador MBR receber o valor do endereço de MAR
 * 
 * Erro:
 * Caso seja fornecido um valor de endereço em MAR acima do máximo definido
 * no alocaMem, o simulador encerra a execução.
 */
void loadMem();

/* Descrição:
 * Procedure para armazenar informações na memória principal. 
 * Utilizado para as funções de STOR em geral. 
 * O campo mask é utilizado para diferenciar os diferentes 
 * tipos de armazenamento:
 * STOR_MASK      = 0X0000000000, função STOR M(X)
 * STORLEFT_MASK  = 0XFF000FFFFF, função STOR M(X,8:19)
 * STORRIGHT_MASK = 0XFFFFFFF000, função STOR M(X,28:39)
 * 
 * Erro:
 * Caso seja fornecido um valor de endereço em MAR acima do máximo 
 * definido no alocaMem, o simulador encerra a execução.
 */
void storMem(unsigned long int mask);

/* Descrição:
 * Função para alocar tamanho de memória informado pelo usuário.
 * int max é o número de palavras de 40 bits que será alocado, 
 * ou seja, MEM[0..max-1].
 * 
 * Retorno:
 * 0 se alocado com sucesso
 * 1 caso contrário
 */
int alocaMem(int max);

/* Descrição: 
 * Função para carregar o programa do arquivo para memória principal.
 * Carrega programa do arquivo FILE * f na memória principal.
 * O parâmetro basedIndex indica o índice de início do arquivo 
 * de entrada.
 * Retorno: 
 * 0 se carregado com sucesso
 * 1 caso contrário
 */
int loadProgram(FILE * f, int basedIndex);

/* Descrição:
 * Procedure que imprime a memória principal.
 * Imprime as numLinhas posições de memória. 
 * 
 * Erro:
 * Se o valor de numLinhas for um valor maior que o definido
 * no alocaMem, o simulador encerra a execução.
 */
void printMem();

void inicializaMem(int sIndexPrint, int eIndexPrint, int max) {
    alocaMem(max);
    startIndexPrint = sIndexPrint;
    endIndexPrint = eIndexPrint;
}

int alocaMem(int max) {
    MEM = (struct palavra *) malloc(sizeof (struct palavra) * max);
    maxMem = max;
    if (!MEM)
        return EXIT_FAILURE;
    return 0;
}

int loadProgram(FILE * f, int basedIndex) {
    struct palavra p;
    struct palavra * ptr = &p;
    int numLidos;
    startIndex = basedIndex;
    int i = startIndex;
    numLidos = fread(ptr, sizeof (p), 1, f);        
    while (!feof(f)) {                
        if (numLidos == 0 && !feof(f))
            return 1;
        if (i <= maxMem)
            MEM[i++] = p;
        else
            return 1;
        numLidos = fread(ptr, sizeof (p), 1, f);        
    }   
    fclose(f);
    return 0;
}

void loadMem() {
    if (uc.MAR < startIndex || uc.MAR >= maxMem) {
        fprintf(stderr, "Erro: Não é possível ler o endereço %u, \n"
                "o endereço ultrapassa os limites da memória!\n", uc.MAR);
        abort();
    }
    ula.MBR = MEM[uc.MAR].p;
}

void storMem(unsigned long int mask) {
    if (uc.MAR < startIndex || uc.MAR >= maxMem) {
        fprintf(stderr, "Erro: Não é possível escrever no endereço %u, \n"
                "o endereço ultrapassa os limites da memória!\n", uc.MAR);
        abort();
    }
    MEM[uc.MAR].p = (MEM[uc.MAR].p & mask) + ula.MBR;
}

void printMem() {
    if (startIndexPrint < startIndex || endIndexPrint >= maxMem) {
        fprintf(stderr, "Erro: Não é possível imprimir o endereço %u, \n"
                "o endereço ultrapassa os limites da memória!\n", uc.MAR);
        abort();
    }
    printf("MEM:\n");
    int i = startIndexPrint;
    while (i <= endIndexPrint) {
        printf("%5d: %010lX\n", i, MEM[i].p);
        i++;
    }
    printf("\n");
}